package team3;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

/**A goodness function which can take the moves made by opponents, and
 * determine which goodness function the opponent is trying to maximize.
 * <p>
 * Determines the goodness function being used by the opponent by
 * repeated evaluations of the min max tree. Once the function is determined,
 * it can be used by LearningAI in min max tree evaluation.
 */
public class LearningFunction implements GoodnessFunction
{
    private AI.Player color;
    private AI.Player otherColor;
    private String reason;

    //the three goodness functions which might be used
    private BoardControlGoodness control;
    private WeightedBoardControlGoodness weightedControl;
    private MobilityGoodness mobility;

    /** The best goodness function so far*/
    private GoodnessFunction goodnessToUse;

    /** Constructs a learning function
     * @param color The color of the player the learning function should help.
     */
    public LearningFunction(AI.Player color)
    {
        this.color = color;
        this.otherColor = (color==AI.Player.BLACK) ? AI.Player.WHITE : AI.Player.BLACK;
        this.control = new BoardControlGoodness();
        this.weightedControl = new WeightedBoardControlGoodness();
        this.mobility = new MobilityGoodness();
        this.goodnessToUse = weightedControl; //the safest default
    }

    /**Evaluates the goodness function that the enemy is beleived to be using.
     * @param state The game state to evaluate for.
     * @param playerToMaximize Player we want winnin'
     * @return A value, such that the greater the value, the better for the player.
     */
    public double evaluateGoodness(BoardState state, AI.Player playerToMaximize)
    {
        return goodnessToUse.evaluateGoodness(state, playerToMaximize);
    }

    /**Updates the goodness function for the last move made by the enemy.
     * <p>
     * Evaluates a min max tree on the state used by the enemy player, to
     * determine which function provides the greatest value for the move made
     * by the enemy.
     */
    public void updateGoodness(int[] lastMove, BoardState previousState)
    {
        double fitness = Double.NEGATIVE_INFINITY;

        //roughly 90% of the time, any move made will maxamize a specific function
        //with a depth of <= 2.
        for (int d = 0; d <= 2; d++)
        {
            Map<GoodnessFunction, Map<int[], Double>> goodnessMaps=
                    createGoodnessMap(previousState, d);

            for(Entry<GoodnessFunction, Map<int[], Double>> goodnessEntry : goodnessMaps.entrySet())
            {
                double newFitness = calculateFitness(lastMove, goodnessEntry.getKey(), goodnessEntry.getValue());
                if (newFitness > fitness)
                {
                    fitness = newFitness;
                    goodnessToUse = goodnessEntry.getKey();
                    reason = "I have determined that you are trying to maximize a ";
                    reason += goodnessToUse.getName()+" goodness with a lookahead ";
                    reason += "depth of " + d + " with a confidence of "+100*fitness;
                    reason += "%. I will now counter that move...";
                }
            }
        }
    }

    /**Determines how well a specific goodness function fits the moves made by a
     * player. Does this by determining the min and max goodness of the function,
     * and the goodness of the move made by the enemy. Expectation is that the
     * closer the move made by the enemy is to the max, taking the difference of
     * min and max into account, the more likely this function was used by the
     * enemy.
     * @param lastMove Move made by the enemy
     * @param function Function being evaluated (used for 'confidence' levels)
     * @param movementMap The results of a min/max tree evaluation.
     * @return (lastMove - min) / (max-min); The relative goodness of the last move, on a 0 to 1 scale.
     */
    private double calculateFitness(int[] lastMove, GoodnessFunction function,  Map<int[], Double> movementMap)
    {
        double min = Double.POSITIVE_INFINITY;
        double max = Double.NEGATIVE_INFINITY;
        double opponent = 0;
        for (Entry<int[], Double> moveEntry : movementMap.entrySet())
        {
            int[] key = moveEntry.getKey();
            double current = moveEntry.getValue();
            if (current > max)
                max = current;
            if (current < min)
                min = current;
            if (key[0] == lastMove[0] && key[1] == lastMove[1])
                opponent = current;
        }
        if(max-min==0) return 0;

        //mobility is a really terrible function, prone to being a false positive
        //  match for many moves; choose it only if there is no better match;
        //  similar for simple control
        double modifier;
        if(function==mobility) modifier=.95;
        else if(function==control) modifier=.98;
        else modifier = 1;

        return modifier*(opponent-min)/(max-min);
    }

    /** Creates a map, from goodness function to results of evaluating the function
     * against a min max tree with depth <code>d</depth>, trying to optimize the
     * tree for the enemy.
     * @param previousState State of the board before the current players turn (e.g. what the enemy saw when deciding their last move)
     * @param d Depth at which to evaluate min max tree
     * @return A map from goodness functions to results of the functions
     */
    private Map<GoodnessFunction, Map<int[], Double>> createGoodnessMap(BoardState previousState, int d)
    {
        MinMaxTree decisionTree = new MinMaxTree(previousState, otherColor);
        Map<GoodnessFunction, Map<int[], Double>> goodnessMap = new HashMap<GoodnessFunction, Map<int[], Double>>();
        goodnessMap.put(control, decisionTree.evaluateTree(d, control));
        goodnessMap.put(weightedControl, decisionTree.evaluateTree(d, weightedControl));
        goodnessMap.put(mobility, decisionTree.evaluateTree(d, mobility));
        return goodnessMap;
    }

    /** @return The reason that the AI made the last move it made.
     */
    public String getReason()
    {
        return reason;
    }

    /**@return The name of the goodness function
     */
    public String getName()
    {
        return "learning";
    }
}

